Skip to content

fix(scripts): resolve override members in the symbol-anchor accept set - #16895

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-16821-symbol-anchors-override
Sep 8, 2026
Merged

fix(scripts): resolve override members in the symbol-anchor accept set#16895
baozhoutao merged 3 commits into
mainfrom
claude/issue-16821-symbol-anchors-override

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #16821

symbolResolutionClass resolved a member declared async / public / protected /
private, and returned unresolved-symbol for the same member declared override. One
token is added to the member matcher's accept set. Nothing that resolved before stops
resolving.

What the regex actually says — read in this branch, not inherited

Line numbers rot, so the site is identified by its token sequence, not its number. In
scripts/symbol-anchors.mjs, scriptSymbolClass builds five declaration matchers; the third
is the member matcher, and its modifier alternation read, verbatim:

(?:readonly |static |public |private |protected |abstract |declare |async |\* )*

Nine alternatives, hand-written, and override is the one TS member modifier absent from
them. Triage named line 336 for that matcher; in this branch it now sits at line 371 because
the explanatory comment landed above it. The alternation above is what identifies it.

The matcher one line up — the function / class / interface / type / enum /
namespace / module declaration matcher — does not carry override either, and correctly
so: override is not a legal modifier on any of those keywords. Only the member matcher was
wrong, and only the member matcher is touched.

Why this is a bug and not a shrug

The enumeration is hand-made and has now been demonstrated incomplete once. The card
raised "should the accept set be derived rather than enumerated?" as an open worry; reading
the source turns it into evidence. A hand-written list of nine that is missing one of the
things it is a list of is not a hypothetical failure mode — it is the observed one.

Two more facts that set the grade, both restated from the card and triage:

  • The refused shape is the one the anchor rule exists for. An override member is where
    a subclass restates a base contract, which is where a citation most needs to be checkable.
  • The failure is loud per author, silent in aggregate. The author sees a red. Nobody sees
    that the corpus censuses were structurally unable to contain an override member, and a
    census number reads like coverage.
  • The unanchorable population is larger than the card knew. Measured on this branch:
    276 override member declarations under packages/, of which 207 are the
    static override metadata on CLI command classes (flags, examples, args,
    description). Every one of them was unanchorable by a symbol anchor.

The change

-    ...|private |protected |abstract |declare |...
+    ...|private |protected |override |abstract |declare |...

Plus the rationale comment above it, twelve self-test cases, and the battery-floor ledger row.
The alternation stays a free-order * group: TS fixes the written order (accessibility,
static, override, readonly, abstract), but pinning that order here would refuse a
spelling for being unidiomatic rather than for being absent, which is not this rule's job.

Census: every corpus that resolves through this matcher, before and after, same stroke

The matcher is shared, so a widened accept set moves every registered census at once. Three
gates import the resolver, not two — check-system-context-census registers a corpus through
the same symbolResolutionClass, and it has a --fix writer, so it is measured here too.

Taken on this branch at commit 1a33d4f4b3 (message-only amend to 7185a8bc1c, identical
tree), all three in one stroke before the edit and again after it:

gate before after
check-adr-symbol-anchors 2067 anchors across 139 records resolve — 277 symbol (251 declaration, 26 literal), 1765 file-level, 25 cross-repo, 6 exempt, 3 continuation. 0 line anchors survive. identical, byte for byte
check-scripts-symbol-anchors 3030 anchors across 233 scripts resolve — 34 symbol (34 declaration, 0 literal), 2996 file-level, 0 cross-repo, 1 exempt, 2 continuation. 0 line anchors on tracked targets survive (4 citations name no tracked file and are not judged; 0 dated allowance finding(s)). identical, byte for byte
check-system-context-census OK — 106 elevation read sites in 20 packages across 45 files, living in 89 symbol(s); the page cites 103 symbol(s) against 103 required, over 127 anchors and 8 file-level citation(s); 22 declared non-read; 9 file(s) hold more than one read in one symbol (the declared precision loss); 18 row reference(s) resolve to their keyed row, 2 declared unheld. identical, byte for byte

diff over the full captured output of each gate reports no change. All three exit 0 before
and after.

Why nothing moved, stated rather than assumed. All three gates were already green, so
every anchor in every corpus already resolved; a purely additive accept set can only move an
anchor's class upwardunresolved to declaration, or literal to declaration — never
down. The only reclassification available was literal to declaration, and none of the ADR
corpus's 26 literal anchors names an override member, so no class changed anywhere.

And the 34 will not move by itself. The fix removes a structural exclusion; it does not
add anchors. That number rises only when an author writes an anchor naming an override
member — which, until this lands, they could not do.

Evidence

In place, in the worktree. The card's own repro copies the module to /tmp and breaks its
relative import of git-env.mjs; that failure is unrelated to the finding, so the resolver was
driven in place instead.

Before the change, against real tree files (not synthetic sources), the same probe after:

                              BEFORE        AFTER
turso-driver.ts#paginationTieBreaker    (protected override)      UNRESOLVED    declaration
turso-driver.ts#supports                (public override get)     UNRESOLVED    declaration
turso-driver.ts#bulkCreate              (override async)          literal       declaration
hook-wrappers.ts#name                   (override readonly)       UNRESOLVED    declaration
turso-driver.ts#noSuchMemberAtAll       (CONTROL: absent)         UNRESOLVED    UNRESOLVED

The control refuses in both legs, so the instrument is live in both. The bulkCreate row is
the class-upgrade case in the wild: it was resolving as literal, the weaker class, because
its name happens to appear as a quoted string in that file. That is the mechanism by which a
census could have moved — and the table above is the measurement showing that in these three
corpora it did not.

Ablation. With the twelve new self-test cases in place, override was removed from the
alternation again and the on-disk mutation verified by counting both the injected and the
removed token before running:

on-disk proof: with-override=0 (expect 0), without-override=1 (expect 1)
ABLATION EXIT=1
❌ symbol-anchors --self-test: a member declared `override` must resolve as a declaration

Restored from HEAD (not from the index) and re-verified: restored blob hash
734d554f099e8a583ba084774d058c12d692c532 equals the HEAD blob, git diff HEAD empty, and
the restore leg is green again. Predicted direction was "turns red"; observed direction was
"turns red".

Self-test. Twelve member spellings, each driven by name, each paired with its own negative
control riding the same source — plain, async, public async, protected async,
private, override, override async, protected override async, public override,
static override, override get, override readonly property. One token per finding is not
the repair: an enumeration is worth exactly what it is provoked with. The battery floor moves
69 to 93 with its ledger row, measured, not guessed.

Gates. All 31 families derived by scripts/pm/dispatch-gates.mjs for this change set were
run; 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN, every one exit 0 — reconciled with
--ran. The three census gates above were run on top of that, since two of them are not in
the derived set for a scripts/** diff.

Card relation, and one residue to name

The relation is declared here in the body only, as Part of #16821 — the implementation is
complete and the card is left for the dispatching seat to close deliberately rather than by
merge parser.

⚠️ Residue, reported rather than rewritten. The two wip: commits at the base of this
branch carry a Refs #16821 trailer, written under a brief that turned out to be wrong:
check:partof-closing-keyword RULE 2 forbids any card-relation trailer in a commit
message, Refs included. The tip commit was amended clean before it was pushed
(commitRelations() returns [] for it); the two earlier ones were already pushed, and
rewriting a pushed branch is not an action this seat takes. They are named here so nobody has
to discover them.

Two things this PR does not do

  1. It does not decide enumerate-vs-derive. Deriving the accept set is a rewrite of a
    shared resolver whose governing ruling is that a corpus joins by registration with no
    second implementation. That is a maintainer's call and is left open, with the evidence for
    it written into the comment at the site so the next author reads it there. The argument is
    in the report back to the dispatching seat.
  2. It re-anchors nothing. [finding] SqlDriver reads keys off caller objects through (obj as any) at 7 sites while 3 parameter types declare none of them — a class, not a third coincidence (after #4311 tenancy, #16570 indexes) #16711's gate header keeps its class-level #TursoDriver
    anchor; this only makes the member-level form possible. Whether that header should be
    revisited is not decided here and is not touched.

Note on the illustration: the card's example anchor named initObjects in
packages/drivers/driver-turso/src/turso-driver.ts. On today's main that file names
initObjects only in comments — the declaration lives in the SqlDriver base — so that exact
anchor would not resolve regardless. The finding itself is unaffected and was re-verified
independently: that file still declares 27 override members of its own, three of which are
used as the live probe above.

No changeset: nothing published moves. Repo-root scripts/ is shipped by no package's
files[], the root manifest is private, and the resolver's exported names appear nowhere
under packages/ (checked with a firing positive control). skip-changeset applied.


Generated by Claude Code

Refs #16821

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
The member matcher in scripts/symbol-anchors.mjs carried a hand-enumerated
nine modifiers -- readonly, static, public, private, protected, abstract,
declare, async and the generator star -- and `override` was the one TS
member modifier absent. A symbol anchor naming a real `override` declaration
returned `unresolved-symbol`, and the gate's own remedy text left the author
only a weaker, file- or class-level anchor.

Adds `override ` to that alternation, a rationale comment recording that the
enumeration is hand-made and has now been demonstrated incomplete once, and
twelve self-test cases -- one per member spelling, each paired with its own
negative control -- with the battery floor moved 69 to 93.

Purely additive: nothing that resolved before stops resolving. All three
corpora that register against this resolver were re-measured before and
after and their output is byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT in substance — PR #16895, reviewed against GitHub and the tree, ⛔ not against the report

Flip + arm withheld until the running checks report. One correction below, and it goes the card's way.

What I re-drove myself

claim how I checked it result
the change is purely additive, one token read the patch protected |override |abstract override inserted into the member alternation only. The declaration matcher one line up is untouched, correctly: override is not legal on function/class/interface/type/enum/namespace/module
12 spellings × 2 = 24 new assertions, floor 69 → 93 read the fixture list and did the arithmetic ✅ 12 rows, each with its own negative control riding the same source, 69 + 24 = 93, and the ledger row is written in the file's existing 63 → 67 → 69 style
276 override members under packages/, 207 of them static override ran my own line-anchored grep on origin/main 276 and 207, exactly. Firing control: 255 lines start with protected . Nonsense control: 0
the new card exists and is unlabelled read #16898 ✅ open, no labels — ⛔ domain:* is triage's, correctly left alone

The self-test is the real deliverable here, not the token. One token would have closed the reported miss and left the enumeration exactly as unprovoked as it was. Twelve spellings each paired with a control means the next absent modifier fails at the list, which is the defect class the card is actually about.

⛔ Correction — one out-of-scope note in the report is WRONG, and the card is right

The report says the card's illustrative anchor turso-driver.ts#initObjects "no longer names a declaration in that file … the declaration lives in the SqlDriver base", and asks that nobody read the card's example as a live repro.

Measured on origin/main:

packages/drivers/driver-turso/src/turso-driver.ts:1681:  override async initObjects(

The declaration is in that file, spelled override async — the exact shape this PR makes resolvable. (SqlDriver.initObjects at driver-sql/src/sql-driver.ts:9972 is the base it overrides; the many other hits in the file are comments, which is presumably what was read.) ⇒ the card's example is a live repro, not a stale one: it named a real declaration, the resolver refused it, and after this change it resolves. That file alone declares 27 override members, every one of them unanchorable before this PR.

⇒ Nothing in the fix changes; the note is struck so the card's evidence is not retired by mistake.

⚠️ What I could NOT verify, stated as a gap

The three-corpus census claim — check-adr-symbol-anchors, check-scripts-symbol-anchors and check-system-context-census byte-identical before and after — is the acceptance row I most wanted to re-drive, and I cannot. This container has no node_modules at all (import('yaml')ERR_MODULE_NOT_FOUND), so these gates refuse with a prerequisite error rather than run. ⛔ A failed read, not a reading, and I am not counting the dev's run as mine.

What I can say from the code rather than from the report: the widening is additive, so an anchor's class can only move upward (unresolved → declaration, literal → declaration) and no anchor can be lost — an unchanged census is therefore the expected outcome, not a suspicious one, and the report names the one in-the-wild case (#bulkCreate, literal → declaration) that could have moved a number and did not because no corpus cites it. The in-seat substitute is CI. ⭐ Finding a third gate on this resolver (check-system-context-census, which the card and this seat both thought was two) is the report's own catch and it widened its own obligation rather than narrowing it.

The two open questions, answered by this seat

  1. Derive vs enumerate → A, keep the enumeration, now provoked. Accepted, and the argument is the right one: the two "derive" routes are a real TS parse in a shared core deliberately kept light, or a permissive modifier-shaped prefix — and the second one's failure mode is silent acceptance, which is the thing this module's whole grade rests on refusing. The enumeration's failure mode is loud, at the author, once. ⇒ The incident argues for provocation, which has landed, not for derivation. ⛔ Nothing is routed to the maintainer, because A is not a rewrite of the shared resolver — triage's constraint 3 fenced a rewrite, and there is none. If anyone later wants B or C, that is a maintainer route and this comment is the record of why it was not taken today.
  2. [finding] SqlDriver reads keys off caller objects through (obj as any) at 7 sites while 3 parameter types declare none of them — a class, not a third coincidence (after #4311 tenancy, #16570 indexes) #16711's class-level anchor → A, leave it. Agreed. The downgraded anchor is this card's evidence; erasing it in the same window costs more than it buys.

⛔ Known residue, and it is MINE

Two pushed commits (f0c04ab788, 22d5b44d47) carry a Refs #16821 trailer, so Part-of PR must not also close its card is red and stays red: once a branch is pushed no author action clears it, and only a forbidden rewrite would. That trailer was written because this seat's dispatch brief told the dev to write itcheck-partof-closing-keyword.mjs RULE 2 forbids all five spellings, Refs included. The dev caught it, amended the tip clean before pushing (commitRelations() returns [] for 7185a8bc1c), and reported the two it could not fix without a forbidden rewrite. ⇒ Correct handling of my defect. It will be landed knowingly with the residue named, exactly as PR #16880 was.

Checks at review time

Part-of PR must not also close its card failure (the residue above, expected) · 5 still running (Lint & Repo Gates, Test Core (1/6), three Type Check legs) · no other failure. Flip + arm once those five report.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants